import {
  Component,
  OnInit,
  ViewEncapsulation,
  ChangeDetectionStrategy,
  Input,
  forwardRef,
  ChangeDetectorRef,
  TemplateRef,
  SimpleChanges,
  OnChanges,
} from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { StandardDate } from '../core/calendar-base/standard-date';
import { CalendarBaseComponent } from '../core/calendar-base/calendar-base.component';
import { I18NService, DateService, InputBoolean, LvDateRangeTag } from '../core/api';
import { LvConfig } from '../core/config';
import { DatePickerCalendarService } from './date-picker-calendar.service';
import { DEFAULT_YEARSOPERATE, LvDateTimeOption, LvYearsOperate } from './interface';

@Component({
  selector: 'lv-date-picker-calendar',
  templateUrl: './date-picker-calendar.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof DatePickerCalendarComponent => DatePickerCalendarComponent),
      multi: true,
    },
    DatePickerCalendarService,
  ],
  host: {
    '[class.lv-date-picker-calendar]': 'true',
    '[class.lv-date-picker-simple]': 'lvPickerMode == "simple"',
  },
})
export class DatePickerCalendarComponent extends CalendarBaseComponent implements OnInit, OnChanges {
  @Input() lvOpenObserver;

  @Input() lvTag: LvDateRangeTag; // for date range picker

  @Input() @InputBoolean() lvShowNowButton;

  @Input() @InputBoolean() lvNowAsBaseTime;

  @Input() @InputBoolean() lvShowTodayButton;

  @Input() lvQuickChangeMode: LvDateRangeTag; // for date range picker

  @Input() lvFooterExtra: TemplateRef<object> | null;

  @Input() lvShowTime: boolean | LvDateTimeOption;

  @Input() lvCalendarCell: TemplateRef<Date> | null;

  @Input() lvCalendarYearCell: TemplateRef<Date> | null;

  @Input() @InputBoolean() lvOnlyShowActiveCell = false; // 是否显示上一月、下一月的单元格

  @Input() @InputBoolean() lvPanelTitleReadonly = false;

  @Input() lvCalendarMonthCell: TemplateRef<Date> | null;

  @Input() lvCalendarYearOperate: LvYearsOperate = DEFAULT_YEARSOPERATE; // 面板头部年份操作

  @Input() lvCalendarMonthOperate: LvYearsOperate = DEFAULT_YEARSOPERATE; // 面板头部月份操作

  activeDateDay;
  activeDateYear;
  activeDateMonth;
  activeTimer = '';
  showDatePanel = true;
  showYearPanel = false;
  showMonthPanel = false;
  showYearByMonth = false;
  timeFormat = 'HH:mm:ss';
  language = LvConfig.language;
  constructor(
    public cdr: ChangeDetectorRef,
    public dateService: DateService,
    public i18n: I18NService,
    public datePickerCalendarService: DatePickerCalendarService,
  ) {
    super(cdr, dateService);
    this.prefixCls = 'lv-date-picker';
    this.datePickerCalendarService.setRootComponent(this);
  }

  get disabledTodayButton(): boolean {
    const date = new StandardDate(new Date());
    return this.checkDateDisableStatus(date);
  }

  // eslint-disable-next-line
  disabledHour = (hour: number): void => {};

  // eslint-disable-next-line
  disabledMinute = (hour: number, minute: number): void => {};

  // eslint-disable-next-line
  disabledSecond = (hour: number, minute: number, second: number): void => {};

  triggerOk(): void {
    this.closeDatePicker();
  }

  trackByIndex(index): any {
    return index;
  }

  activeDateChange(e): void {
    this.lvActiveDateChange.emit(e);
  }

  cellMouseLeave(): void {
    this.lvCellMouseLeave.emit(this.value);
  }

  closeDatePicker(): void {
    this.lvOpenObserver?.next(false);
  }

  // 单击 “今天”、“此刻” 按钮
  selectToday(): void {
    const date = new Date();
    this.doSelect(new StandardDate(date), true);
    this.closeDatePicker();
  }

  renderCalendarUI(): void {
    this.renderCalendarHeader();
    this.renderTimerHeader();
    const { showDatePanel, showMonthPanel, showYearPanel } = this;
    this.renderCalendar({ showDatePanel, showMonthPanel, showYearPanel });
  }

  monthChange(date): void {
    if (this.lvPickerMode === 'month') {
      this.doSelect(date);
      this.closeDatePicker();
    } else {
      this.activeDate = date;
      this.lvActiveDateChange.emit({ tag: this.lvTag, value: this.activeDate });
      this.setPanelState('date');
      this.renderCalendarUI();
    }
  }

  renderCalendarHeader(): void {
    const date = this.activeDate;
    this.activeDateYear = this.dateService.format(date.localDate, 'yyyy');
    this.activeDateMonth = this.dateService.format(date.localDate, this.language === 'en-us' ? 'MMMM' : 'M');
    this.activeDateDay = this.dateService.format(date.localDate, 'd');
  }

  selectTime(date): void {
    let newDate: Date;
    if (this.typeUtils.isNull(this.value)) {
      this.value = new StandardDate(new Date());
    }
    newDate = this.value.setHours(date.getHours());
    newDate = this.value.setMinutes(date.getMinutes());
    newDate = this.value.setSeconds(date.getSeconds());
    this.doSelect(new StandardDate(newDate));
  }

  ngOnInit(): void {
    if (typeof this.lvShowTime === 'object') {
      this.lvShowTime.format && (this.timeFormat = this.lvShowTime.format);
      this.disabledHour = this.lvShowTime.disabledHour;
      this.disabledMinute = this.lvShowTime.disabledMinute;
      this.disabledSecond = this.lvShowTime.disabledSecond;
    }

    this.renderCalendarUI();
    this.setPanelState(this.lvPickerMode);
  }

  doSelect(value, isNow = false): void {
    if (!this.value && !this.lvNowAsBaseTime && !isNow) {
      this.value = new StandardDate(
        new StandardDate(
          new StandardDate(new StandardDate(value.setHours(0)).setMinutes(0)).setSeconds(0),
        ).setMilliseconds(0),
      );
    } else {
      this.value = value;
    }
    this.valueChange.emit(this.value);
    this.renderCalendarUI();
  }

  renderTimerHeader(): void {
    if (this.typeUtils.isNil(this.value)) {
      this.activeTimer = '';
    } else {
      const str = this.dateService.format(
        this.value.localDate,
        ['hh:mm:ss A', 'hh:mm A'].includes(this.timeFormat) ? this.timeFormat.toLocaleLowerCase() : this.timeFormat,
      );
      this.activeTimer = ['hh:mm:ss a', 'hh:mm a'].includes(this.timeFormat) ? str.toLocaleLowerCase() : str;
    }
  }

  addDay(num): void {
    if (this.lvPickerMode === 'simple') {
      this.value = new StandardDate(this.value.addDays(num));
      this.valueChange.emit(this.value);
      this.lvActiveDateChange.emit({ tag: this.lvTag, value: this.value });
    } else {
      this.activeDate = new StandardDate(this.activeDate.addDays(num));
      this.lvActiveDateChange.emit({ tag: this.lvTag, value: this.activeDate });
    }
    this.renderCalendarUI();
  }

  yearChange(date): void {
    if (this.lvPickerMode === 'year') {
      this.doSelect(date);
      this.closeDatePicker();
    } else {
      this.activeDate = date;
      this.lvActiveDateChange.emit({ tag: this.lvTag, value: this.activeDate });
      if (this.showYearByMonth) {
        this.showYearByMonth = false;
        this.setPanelState('month');
      } else {
        this.setPanelState('date');
      }
      this.renderCalendarUI();
    }
  }

  setPanelState(state): void {
    this.showMonthPanel = false;
    this.showDatePanel = false;
    this.showYearPanel = false;
    switch (state) {
      case 'year':
        this.showYearPanel = true;
        break;
      case 'month':
        this.showMonthPanel = true;
        break;
      case 'date':
        this.showDatePanel = true;
        this.renderCalendarHeader();
        break;
      case 'simple':
        break;
      default:
        // eslint-disable-next-line no-console
        console.warn('No match to panel.');
    }
  }

  addYear(num): void {
    if (this.lvCalendarYearOperate?.disableChange) {
      return;
    }
    if (this.lvPickerMode === 'simple') {
      this.value = new StandardDate(this.value.addYears(num));
      this.valueChange.emit(this.value);
      this.lvActiveDateChange.emit({ tag: this.lvTag, value: this.value });
    } else {
      this.activeDate = new StandardDate(this.activeDate.addYears(num));
      this.lvActiveDateChange.emit({ tag: this.lvTag, value: this.activeDate });
    }
    this.renderCalendarUI();
  }

  addMonth(num): void {
    if (this.lvCalendarMonthOperate.disableChange) {
      return;
    }
    if (this.lvPickerMode === 'simple') {
      this.value = new StandardDate(this.value.addMonths(num));
      this.valueChange.emit(this.value);
      this.lvActiveDateChange.emit({ tag: this.lvTag, value: this.value });
    } else {
      this.activeDate = new StandardDate(this.activeDate.addMonths(num));
      this.lvActiveDateChange.emit({ tag: this.lvTag, value: this.activeDate });
    }
    this.renderCalendarUI();
  }

  selectCalendarCell(cell): void {
    if (cell.isDisabled) {
      return;
    }

    if (this.value) {
      const newValue = this.value.clone();
      const newDate = new Date(
        cell.value.getFullYear(),
        cell.value.getMonth(),
        cell.value.getDate(),
        newValue.getHours(),
        newValue.getMinutes(),
        newValue.getSeconds(),
      );
      this.doSelect(new StandardDate(newDate));
    } else {
      this.doSelect(new StandardDate(cell.value));
    }

    if (!this.lvIsRange && !this.lvShowTime) {
      this.closeDatePicker();
    }
  }

  ngOnChanges(changes: SimpleChanges): void {
    const flag =
      changes.lvActiveDate ||
      this.isDateChange(changes.value) ||
      this.isDateChange(changes.lvSelectedValue) ||
      this.isDateChange(changes.lvHoverValue);
    if (flag) {
      this.lvActiveDate && (this.activeDate = this.lvActiveDate);
      this.renderCalendarUI();
    }

    if (changes.lvPickerMode) {
      this.setPanelState(this.lvPickerMode);
    }

    if (changes.lvCalendarYearOperate) {
      this.lvCalendarYearOperate = { ...{}, ...DEFAULT_YEARSOPERATE, ...changes.lvCalendarYearOperate.currentValue };
    }

    if (changes.lvCalendarMonthOperate) {
      this.lvCalendarMonthOperate = { ...{}, ...DEFAULT_YEARSOPERATE, ...changes.lvCalendarMonthOperate.currentValue };
    }
  }

  checkDateDisableStatus(value?): boolean {
    let isDateDisabled = false;
    let isTimeDisabled = false;
    const date = value || null;

    if (date && this.lvDisabledDate) {
      isDateDisabled = this.lvDisabledDate(date.localDate);
    }
    if (date && typeof this.lvShowTime !== 'boolean') {
      const hours = date.getHours();
      const minutes = date.getMinutes();
      const seconds = date.getSeconds();
      isTimeDisabled =
        this.lvShowTime.disabledHour?.(hours) ||
        this.lvShowTime.disabledMinute?.(hours, minutes) ||
        this.lvShowTime.disabledSecond?.(hours, minutes, seconds);
    }
    return !date || isDateDisabled || isTimeDisabled;
  }
}
